package iPersistence.sqlSession;

import iPersistence.pojo.Configuration;
import iPersistence.pojo.MappedStatement;
import iPersistence.utils.GenericTokenParser;
import iPersistence.utils.ParameterMapping;
import iPersistence.utils.ParameterMappingTokenHandler;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Feuler
 * @since 2020/11/6
 **/

public class SimpleExecutor implements Executor {
    private Connection connection;
    private PreparedStatement preparedStatement;

    private void init(Configuration configuration, MappedStatement mappedStatement, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, IllegalAccessException {
        // 1.注册驱动，获取连接
        this.connection = configuration.getDataSource().getConnection();
        this.connection.setAutoCommit(false);
        // 2.获取sql语句
        String sql = mappedStatement.getSql();
        // 3.转换sql语句 #{}转换为?并解析存储#{}中的值
        BoundSql boundSql = getBoundSql(sql);
        // 4.获取预处理对象
        this.preparedStatement = connection.prepareStatement(boundSql.getSql());
        // 5.设置参数
        // 获取参数全路径
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterClass = getClassType(parameterType);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            String content = parameterMapping.getContent();
            // 根据占位符中的值作为名称使用反射获取参数中的属性值
            Field declaredField = parameterClass.getDeclaredField(content);
            // 暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            // preparedStatement变量下标从1开始
            preparedStatement.setObject(i + 1, o);
        }
    }


    @Override
    @SuppressWarnings("unchecked")
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, IllegalAccessException, NoSuchFieldException, InvocationTargetException, IntrospectionException, InstantiationException, NoSuchMethodException {
        init(configuration, mappedStatement, params);
        // 6.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        // 7.封装返回结果集
        ArrayList<Object> results = new ArrayList<>();
        // 获取返回类型全路径
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        while (resultSet.next()) {
            Object o = resultTypeClass.getDeclaredConstructor().newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            // ResultSet中列下标从1开始
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // 获取字段名
                String columnName = metaData.getColumnName(i);
                // 获取字段值
                Object columnValue = resultSet.getObject(columnName);
                // 使用反射或内省，根据数据库表列名和实体属性名对应关系进行封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, columnValue);
            }
            results.add(o);
        }
        closeAllQuietly(connection, preparedStatement, resultSet);
        return (List<E>) results;
    }

    @Override
    public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        init(configuration, mappedStatement, params);
        // 6.执行sql
        int affectRow = preparedStatement.executeUpdate();
        connection.commit();
        closeAllQuietly(connection, preparedStatement);
        return affectRow;
    }

    private Class<?> getClassType(String className) throws ClassNotFoundException {
        if (className != null) {
            return Class.forName(className);
        }
        return null;
    }

    /**
     * 转换sql语句 #{}转换为?并解析存储#{}中的值
     *
     * @param sql 解析前的sql语句
     * @return 解析后的sql语句
     */
    private BoundSql getBoundSql(String sql) {
        // 标记处理类 配置解析器来完成对占位符的解析处理
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // 解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        // 解析出来的占位符中的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        return new BoundSql(parseSql, parameterMappings);
    }

    /**
     * 一键关闭数据库连接
     *
     * @param objects 需要关闭的对象，仅支持传入Connection, Statement, ResultSet
     */
    static void closeAllQuietly(Object... objects) {
        // 需要按顺序关闭，不可直接关闭
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        for (Object object : objects) {
            if (object instanceof Connection) {
                connection = (Connection) object;
            }
            if (object instanceof Statement) {
                statement = (Statement) object;
            }
            if (object instanceof ResultSet) {
                resultSet = (ResultSet) object;
            }
        }
        closeQuietly(resultSet);
        closeQuietly(statement);
        closeQuietly(connection);
    }

    private static void closeQuietly(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                System.out.println("close connection fail!");
            }
        }
    }

    private static void closeQuietly(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                System.out.println("close statement fail!");
            }
        }
    }

    private static void closeQuietly(ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                System.out.println("close resultSet fail!");
            }
        }
    }

}
